/*!
 * hoverIntent v1.8.1 // 2014.08.11 // jQuery v1.9.1+
 * http://briancherne.github.io/jquery-hoverIntent/
 *
 * You may use hoverIntent under the terms of the MIT license. Basically that
 * means you are free to use hoverIntent as long as this header is left intact.
 * Copyright 2007, 2014 Brian Cherne
 */

/* hoverIntent is similar to jQuery's built-in "hover" method except that
 * instead of firing the handlerIn function immediately, hoverIntent checks
 * to see if the user's mouse has slowed down (beneath the sensitivity
 * threshold) before firing the event. The handlerOut function is only
 * called after a matching handlerIn.
 *
 * // basic usage ... just like .hover()
 * .hoverIntent( handlerIn, handlerOut )
 * .hoverIntent( handlerInOut )
 *
 * // basic usage ... with event delegation!
 * .hoverIntent( handlerIn, handlerOut, selector )
 * .hoverIntent( handlerInOut, selector )
 *
 * // using a basic configuration object
 * .hoverIntent( config )
 *
 * @param  handlerIn   function OR configuration object
 * @param  handlerOut  function OR selector for delegation OR undefined
 * @param  selector    selector OR undefined
 * @author Brian Cherne <brian(at)cherne(dot)net>
 */
import jQuery from 'jquery'
;(function (factory) {
    'use strict'
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory)
    } else if (jQuery && !jQuery.fn.hoverIntent) {
        factory(jQuery)
    }
})(function ($) {
    'use strict'

    // default configuration values
    var _cfg = {
        interval: 100,
        sensitivity: 6,
        timeout: 0,
    }

    // counter used to generate an ID for each instance
    var INSTANCE_COUNT = 0

    // current X and Y position of mouse, updated during mousemove tracking (shared across instances)
    var cX, cY

    // saves the current pointer position coordinates based on the given mousemove event
    var track = function (ev) {
        cX = ev.pageX
        cY = ev.pageY
    }

    // compares current and previous mouse positions
    var compare = function (ev, $el, s, cfg) {
        // compare mouse positions to see if pointer has slowed enough to trigger `over` function
        if (
            Math.sqrt((s.pX - cX) * (s.pX - cX) + (s.pY - cY) * (s.pY - cY)) <
            cfg.sensitivity
        ) {
            $el.off(s.event, track)
            delete s.timeoutId
            // set hoverIntent state as active for this element (permits `out` handler to trigger)
            s.isActive = true
            // overwrite old mouseenter event coordinates with most recent pointer position
            ev.pageX = cX
            ev.pageY = cY
            // clear coordinate data from state object
            delete s.pX
            delete s.pY
            return cfg.over.apply($el[0], [ev])
        } else {
            // set previous coordinates for next comparison
            s.pX = cX
            s.pY = cY
            // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
            s.timeoutId = setTimeout(function () {
                compare(ev, $el, s, cfg)
            }, cfg.interval)
        }
    }

    // triggers given `out` function at configured `timeout` after a mouseleave and clears state
    var delay = function (ev, $el, s, out) {
        delete $el.data('hoverIntent')[s.id]
        return out.apply($el[0], [ev])
    }

    $.fn.hoverIntent = function (handlerIn, handlerOut, selector) {
        // instance ID, used as a key to store and retrieve state information on an element
        var instanceId = INSTANCE_COUNT++

        // extend the default configuration and parse parameters
        var cfg = $.extend({}, _cfg)
        if ($.isPlainObject(handlerIn)) {
            cfg = $.extend(cfg, handlerIn)
            if (!$.isFunction(cfg.out)) {
                cfg.out = cfg.over
            }
        } else if ($.isFunction(handlerOut)) {
            cfg = $.extend(cfg, {
                over: handlerIn,
                out: handlerOut,
                selector: selector,
            })
        } else {
            cfg = $.extend(cfg, {
                over: handlerIn,
                out: handlerIn,
                selector: handlerOut,
            })
        }

        // A private function for handling mouse 'hovering'
        var handleHover = function (e) {
            // cloned event to pass to handlers (copy required for event object to be passed in IE)
            var ev = $.extend({}, e)

            // the current target of the mouse event, wrapped in a jQuery object
            var $el = $(this)

            // read hoverIntent data from element (or initialize if not present)
            var hoverIntentData = $el.data('hoverIntent')
            if (!hoverIntentData) {
                $el.data('hoverIntent', (hoverIntentData = {}))
            }

            // read per-instance state from element (or initialize if not present)
            var state = hoverIntentData[instanceId]
            if (!state) {
                hoverIntentData[instanceId] = state = { id: instanceId }
            }

            // state properties:
            // id = instance ID, used to clean up data
            // timeoutId = timeout ID, reused for tracking mouse position and delaying "out" handler
            // isActive = plugin state, true after `over` is called just until `out` is called
            // pX, pY = previously-measured pointer coordinates, updated at each polling interval
            // event = string representing the namespaced event used for mouse tracking

            // clear any existing timeout
            if (state.timeoutId) {
                state.timeoutId = clearTimeout(state.timeoutId)
            }

            // namespaced event used to register and unregister mousemove tracking
            var mousemove = (state.event =
                'mousemove.hoverIntent.hoverIntent' + instanceId)

            // handle the event, based on its type
            if (e.type === 'mouseenter') {
                // do nothing if already active
                if (state.isActive) {
                    return
                }
                // set "previous" X and Y position based on initial entry point
                state.pX = ev.pageX
                state.pY = ev.pageY
                // update "current" X and Y position based on mousemove
                $el.off(mousemove, track).on(mousemove, track)
                // start polling interval (self-calling timeout) to compare mouse coordinates over time
                state.timeoutId = setTimeout(function () {
                    compare(ev, $el, state, cfg)
                }, cfg.interval)
            } else {
                // "mouseleave"
                // do nothing if not already active
                if (!state.isActive) {
                    return
                }
                // unbind expensive mousemove event
                $el.off(mousemove, track)
                // if hoverIntent state is true, then call the mouseOut function after the specified delay
                state.timeoutId = setTimeout(function () {
                    delay(ev, $el, state, cfg.out)
                }, cfg.timeout)
            }
        }

        // listen for mouseenter and mouseleave
        return this.on(
            {
                'mouseenter.hoverIntent': handleHover,
                'mouseleave.hoverIntent': handleHover,
            },
            cfg.selector,
        )
    }
})
